package com.sikaryofficial.backend.service.activity;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.sikaryofficial.backend.config.LotteryRuleConfig;
import com.sikaryofficial.backend.constant.ActivityConstant;
import com.sikaryofficial.backend.constant.ActivityTypeEnum;
import com.sikaryofficial.backend.constant.RedisCacheKey;
import com.sikaryofficial.backend.domain.entity.Activity;
import com.sikaryofficial.backend.domain.vo.ActivityResultCacheVO;
import com.sikaryofficial.backend.domain.vo.PointConsumerVO;
import com.sikaryofficial.backend.domain.vo.UserMonthlyWinVO;
import com.sikaryofficial.backend.service.IActivityPrizeRelationService;
import com.sikaryofficial.backend.service.IActivityResultService;
import com.sikaryofficial.backend.service.IActivityService;
import com.sikaryofficial.common.core.utils.DateUtils;
import com.sikaryofficial.common.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author : qinjinyuan
 * @desc : 指标统计缓存服务
 * @date : 2023/11/03 17:11
 */
@Component
@Slf4j
public class ActivityCacheService {
    @Autowired
    private IActivityService activityService;
    @Autowired
    private IActivityPrizeRelationService relationService;
    @Autowired
    private RedisService redisService;
    @Value("${hismk.backend.stockRatio}")
    private Integer stockRatio;
    @Autowired
    private IActivityResultService activityResultService;
    @Resource
    private LotteryRuleConfig lotteryRuleConfig;

    public void increaseActivityAttendDailyNum(Long activityId, Long userId) {
        String realKey = getActivityAttendDailyNumKey(activityId, userId);
        redisService.setCacheObject(realKey, increaseNum(realKey), 3L, TimeUnit.DAYS);
    }

    public void increaseActivityAttendNum(Long activityId, String param) {
        String realKey = getActivityAttendNumKey(activityId, param);
        // 永不过期
        redisService.increaseNum(realKey);
    }

    private Integer increaseNum(String realKey) {
        Integer num = redisService.getCacheObject(realKey);
        num = Objects.isNull(num) ? 1 : num + 1;
        return num;
    }

    public Integer getActivityAttendDailyNum(Long activityId, Long userId) {
        return redisService.getCacheObject(getActivityAttendDailyNumKey(activityId, userId));
    }

    public Integer getActivityAttendNum(Long activityId, String param) {
        return redisService.getCacheObject(getActivityAttendNumKey(activityId, param));
    }

    public String getActivityAttendDailyNumKey(Long activityId, Long userId) {
        return MessageFormat.format(RedisCacheKey.ACTIVITY_ATTEND_DAILY_NUM_CACHE, activityId.toString(), DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, new Date()), userId.toString());
    }

    public String getActivityAttendNumKey(Long activityId, String param) {
        return MessageFormat.format(RedisCacheKey.ACTIVITY_ATTEND_NUM_CACHE, activityId.toString(), param);
    }

    /**
     * 库存数据 (数据预加载)
     *
     * @return
     */
    public void loadAllPoints() {
        List<Activity> activityList = activityService.list(new LambdaQueryWrapper<Activity>().eq(Activity::getDeletedVersion, 0L)
                // 已上架
                .eq(Activity::getHasEffect, ActivityConstant.EFFECT_YES)
                // 当前时间大于等于开始时间
                .le(Activity::getStartTime, new Date())
                // 当前时间小于等于结束时间
                .ge(Activity::getEndTime, new Date()));
        if (CollUtil.isEmpty(activityList)) {
            return;
        }
        // 加载活动基本信息
        loadAllActivityBaseInfo(activityList);

        List<Long> activityIds = activityList.stream().map(Activity::getActivityId).collect(Collectors.toList());
        // 缓存活动关联的奖项
        updateActivityRelationCache(relationService.getPointConsumerList(activityIds));
        updateActivityResultCacheVOS(relationService.getActivityResultCacheList(activityIds));
        // 更新某人某月的中奖次数
        updateMonthlyPhysicalWinNum(activityResultService.getUserMonthlyWinList());
    }

    public void loadAllActivityBaseInfo(List<Activity> activityList) {
        activityList.forEach(item -> {
            redisService.deleteObject(getActivityBaseInfoKey(item.getActivityId()));
            redisService.setCacheObject(getActivityBaseInfoKey(item.getActivityId()), item);
        });
    }

    public Activity getActivityBaseInfo(Long activityId) {
        return redisService.getCacheObject(getActivityBaseInfoKey(activityId));
    }

    public void updateActivityBaseInfo(Long activityId, Activity activity) {
        redisService.setCacheObject(getActivityBaseInfoKey(activityId), activity);
    }

    public String getActivityBaseInfoKey(Long activityId) {
        return MessageFormat.format(RedisCacheKey.ACTIVITY_BASE_INFO_CACHE, activityId.toString());
    }

    public void updateActivityRelationCache(List<PointConsumerVO> pointConsumerList) {
        if (CollUtil.isEmpty(pointConsumerList)) {
            log.info("无奖项设置数据");
            return;
        }
        Map<Long, List<PointConsumerVO>> activityMap = pointConsumerList.stream().collect(Collectors.groupingBy(PointConsumerVO::getActivityId));
        activityMap.forEach((activityId, pointConsumerVOList) -> {
            redisService.deleteObject(getActivityRelationCacheKey(activityId));
            redisService.setCacheObject(getActivityRelationCacheKey(activityId), pointConsumerVOList);
        });
        // 更新关联缓存
        pointConsumerList.forEach(item -> setStockCache(item.getActivityType(), item.getActivityId(), item.getActivityPrizeId(), item.getStock()));
    }

    public List<PointConsumerVO> getActivityRelationCache(Long activityId) {
        return redisService.getCacheObject(getActivityRelationCacheKey(activityId));
    }

    /**
     * 清理库存缓存
     *
     * @param activityId 活动id
     */
    private String getActivityRelationCacheKey(Long activityId) {
        return MessageFormat.format(RedisCacheKey.ACTIVITY_RELATION_ID_CACHE, activityId.toString());
    }

    private String getActivityPhysicalResultCacheKey(Long activityId, Long userId) {
        return MessageFormat.format(RedisCacheKey.ACTIVITY_PHYSICAL_RESULT_CACHE, activityId.toString(), userId.toString());
    }

    /**
     * 是否已中奖
     *
     * @param activityId 活动ID
     * @param userId     用户ID
     * @param prizeId    奖项ID
     * @return
     */
    public Boolean hasActivityPhysicalResult(Long activityId, Long userId, Long prizeId) {
        if (Objects.isNull(activityId) || Objects.isNull(userId)) {
            return false;
        }
        String friendCacheKey = getActivityPhysicalResultCacheKey(activityId, userId);
        return redisService.isSetMember(friendCacheKey, prizeId);
    }

    /**
     * 更新实物奖项数据
     *
     * @param activityId
     * @param userId
     * @param prizeId
     */
    public void updateActivityPhysicalResult(Long activityId, Long userId, Long prizeId) {
        String physicalResultCacheKey = getActivityPhysicalResultCacheKey(activityId, userId);
        redisService.addCacheSetItem(physicalResultCacheKey, prizeId);
    }

    public void updateActivityResultCacheVOS(List<ActivityResultCacheVO> activityResultCacheVOS) {
        // 清除缓存
        Collection<String> collKeys = redisService.keys(RedisCacheKey.ACTIVITY_PHYSICAL_RESULT_ITEM + "*");
        redisService.deleteObject(collKeys);
        if (CollUtil.isEmpty(activityResultCacheVOS)) {
            return;
        }
        for (ActivityResultCacheVO activityResultCacheVO : activityResultCacheVOS) {
            updateActivityPhysicalResult(activityResultCacheVO.getActivityId(), activityResultCacheVO.getCreatedBy(), activityResultCacheVO.getPrizeId());
        }
    }

    public void updateActivityResultCacheVOS(Long activityId, List<ActivityResultCacheVO> activityResultCacheVOS) {
        // 清除缓存
        Collection<String> collKeys = redisService.keys(MessageFormat.format(RedisCacheKey.ACTIVITY_PHYSICAL_RESULT_PREFIX, activityId.toString()) + "*");
        redisService.deleteObject(collKeys);
        if (CollUtil.isEmpty(activityResultCacheVOS)) {
            return;
        }
        for (ActivityResultCacheVO activityResultCacheVO : activityResultCacheVOS) {
            updateActivityPhysicalResult(activityResultCacheVO.getActivityId(), activityResultCacheVO.getCreatedBy(), activityResultCacheVO.getPrizeId());
        }
    }

    /**
     * 设置活动库存缓存数据
     *
     * @param activityId
     * @param stock
     */
    private void setStockCache(Integer activityType, Long activityId, Long activityPrizeId, Integer stock) {
        ActivityTypeEnum activityTypeEnum = ActivityTypeEnum.getNameByCode(activityType);
        if (ActivityTypeEnum.LOTTERY_TURNTABLE.equals(activityTypeEnum) || ActivityTypeEnum.LOTTERY_SCRATCH.equals(activityTypeEnum)) {
            // 只更新主库存，不更新扩展库存
            redisService.setCacheObject(getCacheStockKey(activityId, activityPrizeId), stock);
            return;
        }
        Integer stockExt = getStockExt(stock);
        Integer realStock = stock - stockExt;
        // 活动库存
        redisService.setCacheObject(getCacheStockKey(activityId, activityPrizeId), realStock);
        // 扩展库存
        redisService.setCacheObject(getCacheStockExtKey(activityId, activityPrizeId), stockExt);
    }

    public void setStockCache(Long activityId) {
        List<PointConsumerVO> pointConsumerList = relationService.getPointConsumer(activityId);
        // 缓存活动关联的奖项
        updateActivityRelationCache(pointConsumerList);
    }

    public void cleanStockCache(Long activityId) {
        List<PointConsumerVO> pointConsumerList = relationService.getPointConsumer(activityId);
        if (CollUtil.isEmpty(pointConsumerList)) {
            log.info("无奖项设置数据");
            return;
        }
        for (PointConsumerVO pointConsumer : pointConsumerList) {
            Long activityPrizeId = pointConsumer.getActivityPrizeId();
            pointConsumer.setStock(0);
            // 活动库存
            String cacheStockKey = this.getCacheStockKey(activityId, activityPrizeId);
            // 扩展库存
            String cacheStockExtKey = this.getCacheStockExtKey(activityId, activityPrizeId);
            ActivityTypeEnum activityTypeEnum = ActivityTypeEnum.getNameByCode(pointConsumer.getActivityType());
            boolean isPurchase = ActivityTypeEnum.PURCHASE.equals(activityTypeEnum) || ActivityTypeEnum.PURCHASE_ACTIVATE_NOW.equals(activityTypeEnum);
            redisService.setCacheObject(cacheStockKey, pointConsumer.getStock());
            if (isPurchase) {
                redisService.setCacheObject(cacheStockExtKey, pointConsumer.getStock());
            }
        }
        // 缓存活动关联的奖项
        updateActivityRelationCache(pointConsumerList);
    }

    /**
     * 实体礼品月度中奖次数限制
     *
     * @param userId
     * @return
     */
    public boolean hasMonthlyPhysicalWinLimit(Long userId) {
        String cacheKey = getUserMonthlyPhysicalWinCountCacheKey(userId);
        Integer winCount = redisService.getCacheObject(cacheKey);
        return winCount != null && winCount >= lotteryRuleConfig.getMonthlyPhysicalWinLimit();
    }

    /**
     * 增加实体礼品月度中奖次数
     *
     * @param userId
     */
    public void addMonthlyPhysicalWinNum(Long userId) {
        String cacheKey = getUserMonthlyPhysicalWinCountCacheKey(userId);
        redisService.increaseNum(cacheKey);
    }

    public void updateMonthlyPhysicalWinNum(List<UserMonthlyWinVO> userMonthlyWinVOS) {
        // 清除缓存
        Collection<String> collKeys = redisService.keys(RedisCacheKey.USER_MONTHLY_PHYSICAL_WIN_COUNT_PREFIX + "*");
        redisService.deleteObject(collKeys);
        if (CollUtil.isEmpty(userMonthlyWinVOS)) {
            return;
        }
        for (UserMonthlyWinVO userMonthlyWinVO : userMonthlyWinVOS) {
            initMonthlyPhysicalWinNum(userMonthlyWinVO.getCreatedBy(), userMonthlyWinVO.getYMonth(), userMonthlyWinVO.getWinCount());
        }
    }

    /**
     * 数据初始化
     *
     * @param userId
     * @param yearMonth
     * @param number
     */
    public void initMonthlyPhysicalWinNum(Long userId, String yearMonth, int number) {
        String cacheKey = getUserMonthlyPhysicalWinCountCacheKey(userId, yearMonth);
        redisService.setCacheObject(cacheKey, number);
    }

    private String getUserMonthlyPhysicalWinCountCacheKey(Long userId, String yearMonth) {
        return MessageFormat.format(RedisCacheKey.USER_MONTHLY_PHYSICAL_WIN_COUNT, yearMonth, userId.toString());
    }

    private String getUserMonthlyPhysicalWinCountCacheKey(Long userId) {
        String yearMonth = DateUtils.parseDateToStr("yyyyMM", new Date());
        return getUserMonthlyPhysicalWinCountCacheKey(userId, yearMonth);
    }

    /**
     * 获取扩展库存
     *
     * @param stock
     * @return
     */
    private Integer getStockExt(Integer stock) {
        return new BigDecimal(stock).multiply(new BigDecimal(stockRatio)).divide(new BigDecimal(100), 0, RoundingMode.HALF_UP).intValue();
    }

    public String getCacheStockExtKey(Long activityId, Long activityPrizeId) {
        return MessageFormat.format(RedisCacheKey.ACTIVITY_STOCK_EXT_DATA_CACHE, activityId.toString(), activityPrizeId.toString());
    }

    public String getCacheStockKey(Long activityId, Long activityPrizeId) {
        return MessageFormat.format(RedisCacheKey.ACTIVITY_STOCK_DATA_CACHE, activityId.toString(), activityPrizeId.toString());
    }

    /**
     * 库存自减
     *
     * @param activityId
     * @param activityPrizeId
     */
    public void updateSignStockCache(Long activityId, Long activityPrizeId) {
        redisService.decrementNum(getCacheStockKey(activityId, activityPrizeId));
    }

    public Integer getRealStock(Long activityId, Long activityPrizeId) {
        return redisService.getCacheObject(getCacheStockKey(activityId, activityPrizeId));
    }

    public Integer getStockExt(Long activityId, Long activityPrizeId) {
        return redisService.getCacheObject(getCacheStockExtKey(activityId, activityPrizeId));
    }
}
